package com.gxar.navigation.server.db.service;

import com.alibaba.fastjson.JSON;
import com.fusion.redis.api.lock.FusionRedissonLock;
import com.fusion.redis.service.RedisService;
import com.gxar.common.exception.APIRuntimeException;
import com.gxar.common.utils.SnowFlakeUtils;
import com.gxar.navigation.server.base.common.constant.MapConstant;
import com.gxar.navigation.server.base.common.constant.RedisKeyConstant;
import com.gxar.navigation.server.base.common.utils.ConcurrentUnorderedMultipleKeyMap;
import com.gxar.navigation.server.base.common.utils.MapSearcher;
import com.gxar.navigation.server.base.common.utils.NearestPointLineCalculateUtils;
import com.gxar.navigation.server.base.model.ResultCodeEnum;
import com.gxar.navigation.server.base.model.arithmetic.dto.Maps;
import com.gxar.navigation.server.base.model.arithmetic.dto.PointDTO;
import com.gxar.navigation.server.base.model.arithmetic.dto.RouteSegmentDTO;
import com.gxar.navigation.server.base.model.node.vo.LocationVO;
import com.gxar.navigation.server.base.model.route.vo.PathInfo;
import com.gxar.navigation.server.base.model.route.vo.request.DirectionRequest;
import com.gxar.navigation.server.base.model.route.vo.response.DirectionResponse;
import com.gxar.navigation.server.db.mapper.LineMapper;
import com.gxar.navigation.server.db.mapper.NodeMapper;
import com.gxar.navigation.server.db.model.LineEntity;
import com.gxar.navigation.server.db.model.NodeEntity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author linsy
 * @version 1.0
 * @created 2023/9/15 15:04
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class RouteService {

    private final RedisService redisRepository;
    private final NodeMapper nodeMapper;
    private final LineMapper lineMapper;
    private final FusionRedissonLock lock;

    /**
     * 获取活动节点集合
     *
     * @param activeId 活动id
     * @return 节点集合
     */
    public List<NodeEntity> getNodeList(Long activeId) {
        Map<Object, Object> nodeMap = redisRepository.hmGetAll(String.format(RedisKeyConstant.GRAPH_NODE_KEY, activeId));
        List<NodeEntity> nodeEntityList;
        if (nodeMap == null || nodeMap.isEmpty()) {
            lock.lock("sync_node_lock:" + activeId, 3600);
            try {
                nodeEntityList = nodeMapper.findByActiveId(activeId);
                if (CollectionUtils.isNotEmpty(nodeEntityList)) {
                    Map<String, String> nodeEntityMap = nodeEntityList.stream().collect(Collectors.toMap(t -> String.valueOf(t.getId()), JSON::toJSONString));
                    redisRepository.hSetEx(String.format(RedisKeyConstant.GRAPH_NODE_KEY, activeId), nodeEntityMap, 3600L, TimeUnit.SECONDS);
                }
            } finally {
                lock.unlock("sync_node_lock:" + activeId);
            }
        } else {
            nodeEntityList = new ArrayList<>();
            nodeMap.forEach((key, value) -> nodeEntityList.add(JSON.parseObject(value.toString(), NodeEntity.class)));
        }
        return nodeEntityList;
    }

    /**
     * 获取具体活动线段集合
     *
     * @param activeId 活动id
     * @return 线段集合
     */
    public List<LineEntity> getLineList(Long activeId) {
        Map<Object, Object> lineMap = redisRepository.hmGetAll(String.format(RedisKeyConstant.GRAPH_LINE_KEY, activeId));
        List<LineEntity> lineEntityList;
        if (lineMap == null || lineMap.isEmpty()) {
            lock.lock("sync_line_lock:" + activeId, 3600);
            try {
                lineEntityList = lineMapper.findByActiveId(activeId);
                if (CollectionUtils.isNotEmpty(lineEntityList)) {
                    Map<String, String> nodeEntityMap = lineEntityList.stream().collect(Collectors.toMap(t -> String.valueOf(t.getId()), JSON::toJSONString));
                    redisRepository.hSetEx(String.format(RedisKeyConstant.GRAPH_LINE_KEY, activeId), nodeEntityMap, 3600L, TimeUnit.SECONDS);
                }
            } finally {
                lock.unlock("sync_line_lock:" + activeId);
            }
        } else {
            lineEntityList = new ArrayList<>();
            lineMap.forEach((key, value) -> lineEntityList.add(JSON.parseObject(value.toString(), LineEntity.class)));
        }
        return lineEntityList;
    }

    /**
     * 路线规划
     *
     * @param activeId         活动
     * @param directionRequest 路线规划请求体
     * @return 路线规划
     */
    public DirectionResponse direction(Long activeId, DirectionRequest directionRequest) {
        // 校验该活动是否有线段
        LineEntity lineEntity = lineMapper.exist(activeId);
        if (Objects.isNull(lineEntity)) {
            throw new APIRuntimeException(ResultCodeEnum.ACTIVE_LINE_NOT_EXIST);
        }
        // 查询目标节点是否存在
        NodeEntity nodeEntity = nodeMapper.findByLonAndLat(directionRequest.getDestination().getLongitude(), directionRequest.getDestination().getLatitude());
        if (Objects.isNull(nodeEntity)) {
            throw new APIRuntimeException(ResultCodeEnum.DIRECTION_NODE_NOT_EXIST);
        }
        List<NodeEntity> nodeList = this.getNodeList(activeId);
        List<LineEntity> lineList = this.getLineList(activeId);
        if (CollectionUtils.isEmpty(nodeList)) {
            throw new APIRuntimeException(ResultCodeEnum.ACTIVE_NODE_NOT_EXIST);
        }
        Map<Long, NodeEntity> nodeEntityMap = nodeList.stream().collect(Collectors.toMap(NodeEntity::getId, t -> t, (t1, t2) -> t1));

        Maps.MapBuilder<Long> mapBuilder = new Maps.MapBuilder<Long>().create();
        // 1、初始化原始节点
        for (NodeEntity node : nodeList) {
            mapBuilder.addNode(Maps.Node.valueOf(node.getId()));
        }
        // 2、初始化临时节点节点
        long startId = Long.parseLong(SnowFlakeUtils.getInstance().nextId());
        mapBuilder.addNode(Maps.Node.valueOf(startId));
        // 3、构建全部线路，用于查找点到最近的线路
        List<RouteSegmentDTO> segments = new ArrayList<>();
        for (LineEntity line : lineList) {
            segments.add(new RouteSegmentDTO(line.getId(), new PointDTO(line.getOrigin(), nodeEntityMap.get(line.getOrigin()).getLatitude(), nodeEntityMap.get(line.getOrigin()).getLongitude()),
                    new PointDTO(line.getDestination(), nodeEntityMap.get(line.getDestination()).getLatitude(), nodeEntityMap.get(line.getDestination()).getLongitude()), line.getRadius()));
        }
        // 4、查找起点在路径半径内允许的最近的路线上
        RouteSegmentDTO pointNearestLine = NearestPointLineCalculateUtils.findPointNearestLine(segments,
                new PointDTO(startId, directionRequest.getOrigin().getLatitude(), directionRequest.getOrigin().getLongitude()));
        // 5、计算临时路径两个点的距离
        double distance = NearestPointLineCalculateUtils.calculateHaversineDistance(new PointDTO(startId, directionRequest.getOrigin().getLatitude(), directionRequest.getOrigin().getLongitude()), pointNearestLine.getEndPoint());
        double distance2 = NearestPointLineCalculateUtils.calculateHaversineDistance(new PointDTO(startId, directionRequest.getOrigin().getLatitude(), directionRequest.getOrigin().getLongitude()), pointNearestLine.getStartPoint());
        BigDecimal distanceDecimal = BigDecimal.valueOf(distance).multiply(BigDecimal.valueOf(MapConstant.DISTANCE_CONVERSION));
        BigDecimal distance2Decimal = BigDecimal.valueOf(distance2).multiply(BigDecimal.valueOf(MapConstant.DISTANCE_CONVERSION));
        // 开始位置到第一段路两个节点的距离分别存储
        Map<Long, Double> tempDistanceMap = new HashMap<>();
        tempDistanceMap.put(pointNearestLine.getEndPoint().getId(), distanceDecimal.doubleValue());
        tempDistanceMap.put(pointNearestLine.getStartPoint().getId(), distance2Decimal.doubleValue());
        // 用来存储图线段无固定顺序key的线段信息，因为目前是无向图，所以开始节点和结束节点可能相反
        ConcurrentUnorderedMultipleKeyMap<Long, Long, LineEntity> lineEntityMap = new ConcurrentUnorderedMultipleKeyMap<>();
        for (LineEntity line : lineList) {
            // 6、将临时线加入路径图中
            if (line.getOrigin().equals(pointNearestLine.getStartPoint().getId()) && line.getDestination().equals(pointNearestLine.getEndPoint().getId())) {
                LineEntity entity = new LineEntity();
                entity.setId(Long.parseLong(SnowFlakeUtils.getInstance().nextId()));
                entity.setDistance(distance2Decimal.longValue());
                entity.setOrigin(pointNearestLine.getStartPoint().getId());
                entity.setDestination(startId);
                this.buildTempLine(entity, line);
                mapBuilder.addPath(entity.getOrigin(), entity.getDestination(), entity.getDistance());

                LineEntity entity2 = new LineEntity();
                entity2.setId(Long.parseLong(SnowFlakeUtils.getInstance().nextId()));
                entity2.setOrigin(startId);
                entity2.setDestination(pointNearestLine.getEndPoint().getId());
                entity2.setDistance(distanceDecimal.longValue());
                this.buildTempLine(entity2, line);
                mapBuilder.addPath(entity2.getOrigin(), entity2.getDestination(), entity2.getDistance());
            } else {
                // 7、将固定路段加入路径图中
                mapBuilder.addPath(line.getOrigin(), line.getDestination(), line.getDistance());
            }
            // 无序存储线的map，开始节点和结束节点可以反过来，目前是无向图
            lineEntityMap.put(line.getOrigin(), line.getDestination(), line);
        }

        // 8、将固定线路构建成图
        Maps<Long> map = mapBuilder.build();
        // 9、创建路径搜索器(每次搜索都需要创建新的MapSearcher)
        MapSearcher<Long> searcher = new MapSearcher<>();
        // 10、创建关闭节点集合
        Set<Long> closeNodeIdsSet = new HashSet<>();
        // 11、设置初始节点
        searcher.init(startId, map, closeNodeIdsSet);
        // 12、获取结果
        MapSearcher.SearchResult<Long> result = searcher.getResult(nodeEntity.getId());
        List<Long> path = result.getPath();
        List<PathInfo> pathInfos = new ArrayList<>(path.size());
        for (int i = 0; i < path.size() - 1; i++) {
            // 第一段为临时路
            // 只有起止节点的经纬度和距离是算出来的，其余信息与相邻的一段路的信息一致
            LineEntity entity;
            PathInfo pathInfo = new PathInfo();
            if (i == 0) {
                // 第一段路为相邻最近的一段路的信息
                entity = lineEntityMap.get(pointNearestLine.getStartPoint().getId(), pointNearestLine.getEndPoint().getId());
                // 取第一段路开始位置到结束位置的距离
                pathInfo.setDistance(BigDecimal.valueOf(tempDistanceMap.get(path.get(i + 1))).divide(BigDecimal.valueOf(MapConstant.DISTANCE_CONVERSION), 6, RoundingMode.HALF_EVEN).doubleValue());
                // 开始位置就是起点
                pathInfo.setOrigin(new LocationVO(directionRequest.getOrigin().getLongitude(), directionRequest.getOrigin().getLatitude()));
            } else {
                // 不是第一段路就是其他的路信息，从无序key的map中取路信息
                entity = lineEntityMap.get(path.get(i), path.get(i + 1));
                // 距离为没段路的固定距离
                pathInfo.setDistance(BigDecimal.valueOf(entity.getDistance()).divide(BigDecimal.valueOf(MapConstant.DISTANCE_CONVERSION), 6, RoundingMode.HALF_EVEN).doubleValue());
                // 开始节点就是path[i]
                pathInfo.setOrigin(new LocationVO(nodeEntityMap.get(path.get(i)).getLongitude(), nodeEntityMap.get(path.get(i)).getLatitude()));
            }
            pathInfo.setDestination(new LocationVO(nodeEntityMap.get(path.get(i + 1)).getLongitude(), nodeEntityMap.get(path.get(i + 1)).getLatitude()));
            pathInfo.setName(entity.getName());
            pathInfo.setOrientation(entity.getOrientation());
            pathInfo.setInstruction(entity.getInstruction());
            pathInfos.add(pathInfo);
        }
        DirectionResponse response = new DirectionResponse();
        response.setOrigin(new LocationVO(directionRequest.getOrigin().getLongitude(), directionRequest.getOrigin().getLatitude()));
        response.setDestination(new LocationVO(directionRequest.getDestination().getLongitude(), directionRequest.getDestination().getLatitude()));
        BigDecimal decimal = BigDecimal.valueOf(result.getDistance()).divide(BigDecimal.valueOf(MapConstant.DISTANCE_CONVERSION), 6, RoundingMode.HALF_EVEN);
        response.setDistance(decimal.doubleValue());
        response.setPaths(pathInfos);
        return response;
    }

    /**
     * 构建临时线段
     *
     * @param tar 目标线段
     * @param ori 原线段
     */
    private void buildTempLine(LineEntity tar, LineEntity ori) {
        tar.setAngle(ori.getAngle());
        tar.setInstruction(ori.getInstruction());
        tar.setOrientation(ori.getOrientation());
        tar.setRadius(ori.getRadius());
        tar.setName(ori.getName());
    }

}
